<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>闭包</title>
</head>

<body>
  <script>
    /**
     * 参考博客：http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
   - 闭包产生的条件?（应用于：单例模式）
     -  一个函数内嵌套另外一个函数
     - 内部函数引用了外部函数的数据(变量/函数)
     即当在一个函数内定义另外一个函数就会产生闭包
     [注意]：闭包存在于嵌套的内部函数中

   - 闭包的特性：
     - 函数嵌套函数；
     - 函数内部可以引用函数外部的参数和变量;
     - 被引用的参数和变量不会被垃圾回收机制回收;

   - 闭包的作用(优点):
       - 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)
       - 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
     问题：
       1.函数执行完后，函数内部声明的局部变量是否还存在？
         一般不存在的，只有闭包中的变量才可存在
       2.让函数外部能直接访问函数内部的局部变量吗?
         不能，只能通过闭包才可以操作函数内部的变量
  
   - 闭包的缺点
     - 增大内存消耗，容易产生内存泄漏

   - 闭包生命周期
       - 函数执行时创建闭包>>执行中闭包没有被其它对象引用>>函数执行结束闭包被销毁
   * */
    function f1() {
      var a = 2; //此时闭包已经产生，原因：内部函数对象已经创建了
      var b = "abc";
      function f2() {
        //执行函数定义就会产生闭包(不用定义内部函数)
        console.log(a);
      }
    }
    f1();

    /**
     * 1.将内部函数作为外部函数的返回值
     * 2.将函数作为实参传递给另一个函数调用
     * */
    //1.将内部函数作为外部函数的返回值
    function fn1() {
      var a = 2; //此时闭包已经产生，原因：内部函数对象已经创建了
      function fn2() {
        a++;
        console.log(a);
      }
      return fn2;
    }
    var f = fn1(); //产生一个闭包
    f();
    f();
    f = null; //让内存函数成为垃圾对象-->回收闭包

    //2.将函数作为实参传递给另一个函数调用
    function showDelay(msg, time) {
      setTimeout(() => {
        console.log(msg);
      }, time);
    }
    showDelay("aaa", 2000);
  </script>

  <script>
    /**
     * 闭包的缺点：
     *   1.函数执行完后，函数内的局部变量没有释放，占用时间会边长
     *   2.容易造成内存泄漏
     * 解决方案：
     *   1.能不用闭包就不用
     *   2.及时释放： f=null 让内存函数成为垃圾对象-->回收闭包
     * */
    function fn3() {
      var arr = new Array(100);
      function fn4() {
        console.log(arr.length);
      }
      return fn4;
    }
    var f2 = fn3();
    f2();
    f2 = null; //让内存函数成为垃圾对象-->回收闭包
  </script>

  <script>
    /**
     * 闭包的作用：
     * 1.从函数外获取内部数据
     * 2.完成读取一个数据和修改这个数据
     * 3.沙箱模式（自调用函数）
     * 4.立即执行函数（匿名闭包）
     * */

    //1.函数外获取内部数据
    function foo() {
      var num = Math.random();
      return function fn() {
        return num;
      };
    }
    var f = foo();
    console.log(f);

    //2.完成读取一个数据和修改这个数据
    function foo() {
      var num = Math.random();
      return {
        get_num: function () {
          return num;
        },
        set_num: function (value) {
          return (num = value);
        },
      };
    }

    //3.沙箱模式（自调用函数）
    (function () {
      var jQuery = function () { };
      jQuery.custom = function () {
        return "jQuery的自定义方法";
      };
      window.jQuery = window.$ = jQuery;
    })();
    console.log($.custom());

    //4.立即执行函数（匿名闭包）
    //经典面试题：以下函数打印什么？
    for (var i = 0; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i); // 6个6
      }, 0);
    }
    //如何打印0，1，2，3，4，5呢？
    // 方法1：闭包解决
    for (var i = 0; i <= 5; i++) {
      (function (i) {
        setTimeout(function timer() {
          console.log(i);
        }, 0)
      })(i)
    }

    // 方法2：var更改为let(推荐使用)
    for (let i = 0; i <= 5; i++) {
      setTimeout(function timer() {
        console.log(i); // 0  1 2 3 4 5 
      }, 0);
    }
  </script>
</body>

</html>